package com.lechi.web.server;

import com.lechi.iot.android.channel.IIotTcpChannel;
import com.lechi.iot.android.channel.IotTcpChannelGroup;
import com.lechi.iot.android.device.IotTcpControlCentre;
import com.lechi.iot.android.device.IotTcpDeviceChannel;
import com.lechi.iot.netty.handle.TcpExceptionHandler;
import com.lechi.web.constants.Constants;
import com.lechi.web.server.connect.TcpServerConnect;
import com.lechi.web.server.handle.BeiDouInboundMessageHandler;
import com.lechi.web.server.handle.Weather2017InboundMessageHandler;
import com.lechi.web.server.handle.Weather2024InboundMessageHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 明文字典报文的通讯服务端
 */
@Slf4j
public class WeatherServer implements IotTcpControlCentre {

    private static int IDLE_TIMEOUT = 600_000;

    private final int port;

    private final int workThread;

    private final String protocolType;

    private EventLoopGroup bossGroup;

    private EventLoopGroup workerGroup;

    private ChannelFuture future;

    @Getter
    private IotTcpChannelGroup groups;

    /**
     * 创建指定服务端口，指定线程数的服务端
     *
     * @param port          服务端口
     * @param workThread    执行线程池线程数
     * @param heartBeatTime 心跳检测超时时间(单位：毫秒)
     */
    public WeatherServer(int port, int workThread, int heartBeatTime, String protocol) {
        this.port = port;
        this.workThread = workThread;
        this.protocolType = protocol;
        IDLE_TIMEOUT = heartBeatTime;
        this.groups = new IotTcpChannelGroup();
    }

    /**
     * 启动服务
     */
    public void start() {

        if (Epoll.isAvailable()) {
            bossGroup = new EpollEventLoopGroup(1);
            workerGroup = new EpollEventLoopGroup(workThread);
        } else {
            bossGroup = new NioEventLoopGroup(1);
            workerGroup = new NioEventLoopGroup(workThread);
        }
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) //ServerSocketChannel 是一个用于监听客户端连接请求的通道。它是一个可以注册为非阻塞操作的服务器套接字通道，允许做多路复用和异步I/O操作。
                .handler(new LoggingHandler(LogLevel.INFO)) //绑定到端口并监听客户端连接请求
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) //是Netty提供的一个内存分配器，它使用池化内存来提高性能。 Boss线程池内存池配置.
                .option(ChannelOption.SO_REUSEADDR, true) //option()设置的是服务端用于接收进来的连接，也就是boosGroup线程。
                .option(EpollChannelOption.SO_REUSEPORT, true)
                .option(ChannelOption.SO_BACKLOG, 1024) // 指定了内核为此套接口排队的最大连接个数。对于高并发场景，可以适当增大该值，
                .childOption(ChannelOption.TCP_NODELAY, true) // 禁用了Nagle算法，允许小包的发送  childOption()是父管道接收到的连接，也就是workerGroup线程。
                .childOption(ChannelOption.SO_KEEPALIVE, true) //启用 TCP 保持活动机制，定期发送心跳包检测连接状态
                .childOption(ChannelOption.SO_LINGER, -1)  // 默认值为-1，表示禁用该功能 表示socket.close()方法立即返回，但OS底层会将发送缓冲区的数据全部发送到对端
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ChannelPipeline channelPipeline = ch.pipeline();
                        IIotTcpChannel deviceChannel = new IotTcpDeviceChannel(ch);
                        add(deviceChannel);
                        channelPipeline.addLast(new IdleStateHandler(0, 0, IDLE_TIMEOUT, TimeUnit.SECONDS));
                        channelPipeline.addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                if (evt instanceof IdleStateEvent) {
                                    log.error("WEATHER2017服务端Socket服务指定时间 {} 范围内没有读写操作，断开连接， ：", IDLE_TIMEOUT);
                                    ctx.disconnect();
                                } else {
                                    super.userEventTriggered(ctx, evt);
                                }
                            }
                        });
                        channelPipeline.addLast(new DelimiterBasedFrameDecoder(2048, Unpooled.copiedBuffer("\r\n".getBytes())));
                        if (Constants.WEATHER2017.equals(protocolType)) {
                            channelPipeline.addLast(new Weather2017InboundMessageHandler());
                        } else if (Constants.WEATHER2024.equals(protocolType)) {
                            channelPipeline.addLast(new Weather2024InboundMessageHandler());
                        } else if (Constants.BEIDOU_RSMC.equals(protocolType)) {
                            channelPipeline.addLast(new BeiDouInboundMessageHandler());
                        }
                        channelPipeline.addLast(new StringEncoder());
                        channelPipeline.addLast(new TcpServerConnect(protocolType));
                        channelPipeline.addLast(new TcpExceptionHandler(protocolType));
                    }
                });
        future = b.bind(port);
    }






    /**
     * 停止服务
     */
    public void stop() {
        future.channel().closeFuture();
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

    public List<IIotTcpChannel> groups() {
        return groups.list();
    }

    @Override
    public boolean add(IIotTcpChannel channel) {
        return groups.add(channel);
    }

    @Override
    public IIotTcpChannel remove(String name) {
        return groups.remove(name);
    }

    @Override
    public IIotTcpChannel get(String name) {
        return groups.get(name);
    }

}
